package com.grid.camera.pelco;

import com.grid.camera.exception.CommandFailedException;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

/** Pelco D 增强客户端 封装PelcoDProtocol公共方法，添加socket请求、重试机制和返回结果校验功能 */
@Slf4j
public class PelcoDEnhancedClient implements AutoCloseable {
    private final PelcoDTcpClient tcpClient;
    private final int defaultTimeout;
    private final int maxRetries;
    private final int retryDelay;
    private final boolean validateResponses;

    /**
     * 构造函数
     *
     * @param host 摄像机IP地址
     * @param port TCP端口
     * @param deviceAddress 设备地址 (1-255)
     */
    public PelcoDEnhancedClient(String host, int port, int deviceAddress) {
        this(host, port, deviceAddress, 3000, 3, 500, true);
    }

    /**
     * 构造函数
     *
     * @param host 摄像机IP地址
     * @param port TCP端口
     * @param deviceAddress 设备地址 (1-255)
     * @param timeout 默认超时时间（毫秒）
     * @param maxRetries 最大重试次数
     * @param retryDelay 重试延迟时间（毫秒）
     * @param validateResponses 是否验证响应
     */
    public PelcoDEnhancedClient(
            String host,
            int port,
            int deviceAddress,
            int timeout,
            int maxRetries,
            int retryDelay,
            boolean validateResponses) {
        this.tcpClient = new PelcoDTcpClient(host, port, deviceAddress);
        this.defaultTimeout = timeout;
        this.maxRetries = maxRetries;
        this.retryDelay = retryDelay;
        this.validateResponses = validateResponses;

        // 配置TCP客户端
        tcpClient.setConnectionTimeout(timeout);
        tcpClient.setReadTimeout(timeout);
        tcpClient.setMaxRetries(maxRetries);
        tcpClient.setRetryDelay(retryDelay);
    }

    /**
     * 清除预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] clearPreset(int presetNumber) throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.clearPreset(address, presetNumber);
        return executeCommand(command);
    }

    @Override
    public void close() {
        tcpClient.close();
    }

    /**
     * 连接到摄像机
     *
     * @return 是否连接成功
     * @throws IOException 连接异常
     */
    public boolean connect() throws IOException {
        return tcpClient.connect();
    }

    /**
     * 执行命令并验证响应
     *
     * @param command 命令字节数组
     * @param validator 响应验证器
     * @param timeout 超时时间（毫秒）
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] executeCommand(byte[] command, Predicate<byte[]> validator, int timeout)
            throws IOException, CommandFailedException {
        log.info("执行命令: {}", PelcoDUtils.bytesToHex(command));

        int attempts = 0;
        IOException lastException = null;

        while (attempts <= maxRetries) {
            try {
                // 发送命令并等待响应
                byte[] response = tcpClient.sendCommandAndWaitResponse(command, timeout);

                // 检查响应
                if (response == null) {
                    log.warn("命令执行无响应，尝试 {}/{}", attempts + 1, maxRetries + 1);
                    attempts++;
                    if (attempts <= maxRetries) {
                        Thread.sleep(retryDelay);
                        continue;
                    }
                    throw new CommandFailedException("命令执行无响应");
                }

                log.debug("收到响应: {}", PelcoDUtils.bytesToHex(response));

                // 验证响应
                if (!validateResponses || validator == null || validator.test(response)) {
                    return response;
                } else {
                    log.warn("响应验证失败，尝试 {}/{}", attempts + 1, maxRetries + 1);
                    attempts++;
                    if (attempts <= maxRetries) {
                        Thread.sleep(retryDelay);
                        continue;
                    }
                    throw new CommandFailedException("响应验证失败: " + PelcoDUtils.bytesToHex(response));
                }
            } catch (IOException e) {
                lastException = e;
                log.warn("命令执行异常: {}，尝试 {}/{}", e.getMessage(), attempts + 1, maxRetries + 1);
                attempts++;
                if (attempts <= maxRetries) {
                    try {
                        Thread.sleep(retryDelay);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("重试被中断", ie);
                    }
                    continue;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("重试被中断", e);
            }
        }

        if (lastException != null) {
            throw lastException;
        }

        throw new CommandFailedException("命令执行失败，已重试" + maxRetries + "次");
    }

    /**
     * 执行命令并验证响应（使用默认超时时间）
     *
     * @param command 命令字节数组
     * @param validator 响应验证器
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] executeCommand(byte[] command, Predicate<byte[]> validator)
            throws IOException, CommandFailedException {
        return executeCommand(command, validator, defaultTimeout);
    }

    /**
     * 执行命令（不验证响应）
     *
     * @param command 命令字节数组
     * @param timeout 超时时间（毫秒）
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] executeCommand(byte[] command, int timeout)
            throws IOException, CommandFailedException {
        return executeCommand(command, null, timeout);
    }

    /**
     * 执行命令（使用默认超时时间，不验证响应）
     *
     * @param command 命令字节数组
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] executeCommand(byte[] command) throws IOException, CommandFailedException {
        return executeCommand(command, null, defaultTimeout);
    }

    /**
     * 异步执行命令
     *
     * @param command 命令字节数组
     * @param validator 响应验证器
     * @param timeout 超时时间（毫秒）
     * @return 包含响应的CompletableFuture
     */
    public CompletableFuture<byte[]> executeCommandAsync(
            byte[] command, Predicate<byte[]> validator, int timeout) {
        return CompletableFuture.supplyAsync(
                () -> {
                    try {
                        return executeCommand(command, validator, timeout);
                    } catch (IOException | CommandFailedException e) {
                        throw new RuntimeException(e);
                    }
                });
    }

    /**
     * 异步执行命令（使用默认超时时间）
     *
     * @param command 命令字节数组
     * @param validator 响应验证器
     * @return 包含响应的CompletableFuture
     */
    public CompletableFuture<byte[]> executeCommandAsync(
            byte[] command, Predicate<byte[]> validator) {
        return executeCommandAsync(command, validator, defaultTimeout);
    }

    /**
     * 异步执行命令（不验证响应）
     *
     * @param command 命令字节数组
     * @param timeout 超时时间（毫秒）
     * @return 包含响应的CompletableFuture
     */
    public CompletableFuture<byte[]> executeCommandAsync(byte[] command, int timeout) {
        return executeCommandAsync(command, null, timeout);
    }

    /**
     * 异步执行命令（使用默认超时时间，不验证响应）
     *
     * @param command 命令字节数组
     * @return 包含响应的CompletableFuture
     */
    public CompletableFuture<byte[]> executeCommandAsync(byte[] command) {
        return executeCommandAsync(command, null, defaultTimeout);
    }

    /**
     * 执行命令并验证响应是否与请求完全相同
     *
     * @param command 命令字节数组
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public byte[] executeCommandWithExactMatch(byte[] command)
            throws IOException, CommandFailedException {
        return executeCommand(command, PelcoDResponseValidator.exactMatch(command));
    }

    /**
     * 调用预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 操作结果
     */
    public boolean gotoPreset(int presetNumber) throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.gotoPreset(address, presetNumber);
        byte[] response = executeCommand(command);

        String commandString = PelcoDUtils.bytesToHex(command);
        String responseString = PelcoDUtils.bytesToHex(response);

        responseString = responseString.replaceAll("\\s+", "");

        log.error("responseString:{}", responseString);

        String flag = responseString.substring(4, 8);

        log.error("flag:{}", flag);

        if (commandString.equals(responseString)) {
            return true;
        }

        if (flag.equals("0017")) {
            throw new CommandFailedException("水平超最大长度");
        }

        if (flag.equals("0027")) {
            throw new CommandFailedException("垂直超最大长度");
        }

        if (flag.equals("0037")) {
            throw new CommandFailedException("正在执行预置位");
        }

        if (flag.equals("0000")) {
            throw new CommandFailedException("未自检");
        }

        return false;
    }

    /**
     * 检查是否已连接
     *
     * @return 是否已连接
     */
    public boolean isConnected() {
        return tcpClient.isConnected();
    }

    /**
     * 云台下移
     *
     * @return 成功标识
     */
    public boolean moveDown() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.moveDown(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 停止云台下移
     *
     * @return 成功标识
     */
    public boolean moveDownStop() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.moveDownStop(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 云台上移
     *
     * @return 成功标识
     */
    public boolean moveUp() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.moveUp(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 停止云台上移
     *
     * @return 成功标识
     */
    public boolean moveUpStop() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.moveUpStop(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 读取硬件版本
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readHardwareVersion() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.readHardwareVersion(address);
        byte[] response = executeCommand(command);
        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readPreset(int presetNumber) throws IOException, CommandFailedException {

        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.readPreset(address, presetNumber);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取侧障碍物距离
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readSideObstacle() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.readSideObstacle(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取软件版本
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readSoftwareVersion() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.readSoftwareVersion(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取云台状态信息
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readStatus() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.readStatus(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取垂直方向驱动状态
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readVerticalDriveStatus() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.readVerticalDriveStatus(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取云台垂直编码器状态
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readVerticalEncoderStatus() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.readVerticalEncoderStatus(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 读取云台垂直速度
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public String readVerticalSpeed() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.readVerticalSpeed(address);
        byte[] response = executeCommand(command);

        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 复位系统
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public boolean resetSystem() throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.resetSystem(address);

        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 设置垂直轨道最大位置
     *
     * @return 响应字节数组
     * @throws IOException 通信异常
     * @throws CommandFailedException 命令执行失败异常
     */
    public boolean setMaxPosition() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.setMaxPosition(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 设置预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     */
    public boolean setPreset(int presetNumber) throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.setPreset(address, presetNumber);

        boolean result = false;

        byte[] commandResult = executeCommand(command);

        String response = PelcoDUtils.bytesToHex(commandResult);
        response = response.replaceAll("\\s+", "");

        String flage = response.substring(4, 8);

        if (flage.equals("0300")) {
            throw new CommandFailedException("setPreset failed");
        }

        if (flage.equals("0301")) {
            result = true;
        }

        if (flage.equals("0000")) {
            throw new CommandFailedException("未自检");
        }

        return result;
    }

    /**
     * 设置侧障碍物距离
     *
     * @param distance
     * @return
     * @throws IOException
     * @throws CommandFailedException
     */
    public String setSideObstacle(int distance) throws IOException, CommandFailedException {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.setSideObstacle(address, distance);
        byte[] response = executeCommand(command);
        return PelcoDUtils.bytesToHex(response);
    }

    /**
     * 设置云台垂直速度
     *
     * @param speed 速度值
     * @return 响应字节数组
     */
    public boolean setVerticalSpeed(int speed) {
        int address = tcpClient.getDeviceAddress();

        byte[] command = PelcoDProtocol.setVerticalSpeed(address, speed);

        return checkEexecuteCommandWithExactMatch(command);
    }

    /**
     * 停止所有移动
     *
     * @return 成功标识
     */
    public boolean stopMovement() {
        int address = tcpClient.getDeviceAddress();
        byte[] command = PelcoDProtocol.stopMovement(address);
        return checkEexecuteCommandWithExactMatch(command);
    }

    private boolean checkEexecuteCommandWithExactMatch(byte[] command) {
        boolean result = false;

        try {
            executeCommandWithExactMatch(command);
            result = true;
        } catch (Exception e) {
            log.error("操作失败", e.getMessage());
        }

        return result;
    }

    /**
     * 创建默认的响应验证器 验证响应的地址字节与命令中的地址字节相匹配
     *
     * @param command 命令字节数组
     * @return 响应验证器
     */
    private Predicate<byte[]> createDefaultValidator(byte[] command) {
        return response -> {
            // 检查响应长度
            if (response.length < 2) {
                return false;
            }

            // 验证地址字节匹配
            return response[1] == command[1];
        };
    }
}
